﻿using System;
using System.Diagnostics.Contracts;

namespace MathService
{
    public class Interval<T> :
        IEquatable<Interval<T>>, ICloneable<Interval<T>> where T : IComparable<T>
    {
        /* ------------------------------------------------------------------------------------------ */

        #region Поля

        /// <summary>Включена ли нижняя граница интервала?</summary>
        private bool f_MinInclude = true;
        /// <summary>Включена ли верхняя граница интервала?</summary>
        private bool f_MaxInclude = true;
        /// <summary>Нижняя граница интервала</summary>
        private T f_Min;
        /// <summary>Верхняя граница интервала</summary>
        private T f_Max;

        #endregion

        /* ------------------------------------------------------------------------------------------ */

        #region Свойства

        /// <summary>Включена ли нижняя граница интервала?</summary>
        public bool MinInclude
        {
            [Pure, System.Diagnostics.DebuggerStepThrough]
            get { return f_MinInclude; }
            [System.Diagnostics.DebuggerStepThrough]
            set { f_MinInclude = value; }
        }
        /// <summary>Включена ли верхняя граница интервала?</summary>
        public bool MaxInclude
        {
            [Pure, System.Diagnostics.DebuggerStepThrough]
            get { return f_MaxInclude; }
            [System.Diagnostics.DebuggerStepThrough]
            set { f_MaxInclude = value; }
        }

        /// <summary>Нижняя граница интервала</summary>
        public T Min
        {
            [Pure, System.Diagnostics.DebuggerStepThrough]
            get
            {
                Contract.Ensures(Contract.Result<T>() != null);
                return f_Min;
            }
            [System.Diagnostics.DebuggerStepThrough]
            set
            {
                Contract.Requires(value != null);
                Contract.Ensures(f_Min != null);
                Contract.Ensures(f_Max != null);
                if(f_Max.CompareTo(value) >= 0)
                    f_Min = value;
                else
                {
                    f_Min = f_Max;
                    f_Max = value;
                }
            }
        }
        /// <summary>Верхняя граница интервала</summary>
        public T Max
        {
            [Pure, System.Diagnostics.DebuggerStepThrough]
            get
            {
                Contract.Ensures(Contract.Result<T>() != null);
                return f_Max;
            }
            [System.Diagnostics.DebuggerStepThrough]
            set
            {
                Contract.Requires(value != null);
                Contract.Ensures(f_Min != null);
                Contract.Ensures(f_Max != null);
                if(f_Min.CompareTo(value) <= 0)
                    f_Max = value;
                else
                {
                    f_Max = f_Min;
                    f_Min = value;
                }
            }
        }

        #endregion

        /* ------------------------------------------------------------------------------------------ */

        #region Конструкторы

        /// <summary>Интервал</summary>
        /// <param name="Min">Нижняя граница интервала</param>
        /// <param name="Max">Верзняя граница интервала</param>
        [System.Diagnostics.DebuggerStepThrough]
        public Interval(T Min, T Max)
        {
            Contract.Requires(Max != null);
            Contract.Requires(Min != null);
            Contract.Ensures(f_Max != null);
            Contract.Ensures(f_Min != null);
            f_Max = Max;
            f_Min = Min;
        }

        /// <summary>Интервал</summary>
        /// <param name="Min">Нижняя граница интервала</param>
        /// <param name="Max">Верхняя граница интервала</param>
        /// <param name="IncludeLimits">Включать пределы? (default:true)</param>
        [System.Diagnostics.DebuggerStepThrough]
        public Interval(T Min, T Max, bool IncludeLimits)
            : this(Min, IncludeLimits, Max, IncludeLimits)
        {
            Contract.Requires(Max != null);
            Contract.Requires(Min != null);
            Contract.Ensures(f_Max != null);
            Contract.Ensures(f_Min != null);
        }

        /// <summary>Интервал</summary>
        /// <param name="Min">Нижняя граница интервала</param>
        /// <param name="MinInclude">Включена ли нижняя граница интервала?</param>
        /// <param name="Max">Верхняя граница интервала</param>
        [System.Diagnostics.DebuggerStepThrough]
        public Interval(T Min, bool MinInclude, T Max)
            : this(Min, Max)
        {
            Contract.Requires(Max != null);
            Contract.Requires(Min != null);
            Contract.Ensures(f_Max != null);
            Contract.Ensures(f_Min != null);
            f_MinInclude = MinInclude;
        }

        /// <summary>Интервал</summary>
        /// <param name="Min">Нижняя граница интервала</param>
        /// <param name="MinInclude">Включена ли нижняя граница интервала?</param>
        /// <param name="Max">Верхняя граница интервала</param>
        /// <param name="MaxInclude">Включена ли верхняя граница интервала</param>
        [System.Diagnostics.DebuggerStepThrough]
        public Interval(T Min, bool MinInclude, T Max, bool MaxInclude)
            : this(Min, MinInclude, Max)
        {
            Contract.Requires(Max != null);
            Contract.Requires(Min != null);
            Contract.Ensures(f_Max != null);
            Contract.Ensures(f_Min != null);
            f_MaxInclude = MaxInclude;
        }

        #endregion

        /* ------------------------------------------------------------------------------------------ */

        [Pure, System.Diagnostics.DebuggerStepThrough]
        public T Normalize(T Value)
        {
            Contract.Requires(Value != null);
            Contract.Ensures(Contract.Result<T>() != null);
            return Value.CompareTo(f_Max) > 0 ? f_Max : (Value.CompareTo(f_Min) < 0 ? f_Min : Value);
        }

        [Pure, System.Diagnostics.DebuggerStepThrough]
        public bool Check(T Value)
        {
            Contract.Requires(Value != null);

            return (f_MinInclude && f_Min.CompareTo(Value) == 0)
                   || (f_MaxInclude && f_Max.CompareTo(Value) == 0)
                   || (Value.CompareTo(f_Min) > 0 && Value.CompareTo(f_Max) < 0);
        }

        [Pure, System.Diagnostics.DebuggerStepThrough]
        public bool IsInclude(Interval<T> I)
        {
            Contract.Requires(I != null);

            return Check(I.Min) && Check(I.Max);
        }

        [Pure, System.Diagnostics.DebuggerStepThrough]
        public bool IsIntersect(Interval<T> I)
        {
            Contract.Requires(I != null);

            var minToMinCompare = I.f_Min.CompareTo(f_Min);
            var minToMaxCompare = I.f_Min.CompareTo(f_Max);

            var maxToMinCompare = I.f_Max.CompareTo(f_Min);
            var maxToMaxCompare = I.f_Max.CompareTo(f_Max);


            if((maxToMinCompare < 0 && minToMinCompare < 0) || (minToMaxCompare > 0 && maxToMaxCompare > 0)) return false;

            if(minToMinCompare < 0)
                return maxToMinCompare > 0 || MinInclude && I.MaxInclude;

            if(maxToMaxCompare > 0)
                return minToMaxCompare < 0 || MaxInclude && I.MinInclude;

            throw new NotImplementedException("Ошибка реализации метода проверки на пересечение интервалов " + this + "|" + I);
        }

        public Interval<T> GetInvertedInterval() { return new Interval<T>(Max, MaxInclude, Min, MinInclude); }

        public void WhileInInterval(T start, Action<T> Do, Func<T, T> Pos)
        {
            var x = start;
            while(Check(x))
            {
                Do(x);
                x = Pos(x);
            }
        }

        /* ------------------------------------------------------------------------------------------ */

        #region Базовые методы

        /// <summary>Играет роль хэш-функции для определенного типа. </summary>
        /// <returns>Хэш-код для текущего объекта <see cref="T:System.Object"/>.</returns>
        /// <filterpriority>2</filterpriority>
        [Pure, System.Diagnostics.DebuggerStepThrough]
        public override int GetHashCode()
        {
            unchecked
            {
                var result = f_MinInclude.GetHashCode();
                result = (result * 397) ^ f_MaxInclude.GetHashCode();
                result = (result * 397) ^ f_Min.GetHashCode();
                result = (result * 397) ^ f_Max.GetHashCode();
                return result;
            }
        }

        /// <summary>Указывает, равен ли текущий объект другому объекту того же типа.</summary>
        /// <returns>true, если текущий объект равен параметру <paramref name="other"/>, в противном случае — false.</returns>
        /// <param name="other">Объект, который требуется сравнить с данным объектом.</param>
        [Pure, System.Diagnostics.DebuggerStepThrough]
        public bool Equals(Interval<T> other)
        {
            return !ReferenceEquals(null, other)
                && (ReferenceEquals(this, other)
                    || (other.f_MinInclude == f_MinInclude
                        && other.f_MaxInclude == f_MaxInclude
                        && other.f_Min.Equals(f_Min)
                        && other.f_Max.Equals(Max)));
        }

        /// <summary>
        /// Определяет, равен ли заданный объект <see cref="T:System.Object"/> текущему объекту <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// true, если указанный объект <see cref="T:System.Object"/> равен текущему объекту <see cref="T:System.Object"/>; в противном случае — false.
        /// </returns>
        /// <param name="obj">Объект <see cref="T:System.Object"/>, который требуется сравнить с текущим объектом <see cref="T:System.Object"/>.</param>
        /// <exception cref="T:System.NullReferenceException">Параметр <paramref name="obj"/> имеет значение null.</exception><filterpriority>2</filterpriority>
        [Pure, System.Diagnostics.DebuggerStepThrough]
        public override bool Equals(object obj)
        {
            return (!ReferenceEquals(null, obj) && (ReferenceEquals(this, obj))
                || (obj is Interval<T> && Equals(obj as Interval<T>)));
        }

        /// <summary>Создает новый объект, который является копией текущего экземпляра.</summary>
        /// <returns>Новый объект, являющийся копией этого экземпляра.</returns>
        /// <filterpriority>2</filterpriority>
        //[System.Diagnostics.DebuggerStepThrough]
        object ICloneable.Clone()
        {
            return Clone();
        }

        //[System.Diagnostics.DebuggerStepThrough]
        public Interval<T> Clone()
        {
            Contract.Ensures(Contract.Result<Interval<T>>() != null);
            return new Interval<T>(f_Min, f_MinInclude, f_Max, f_MaxInclude);
        }

        /// <summary>
        /// Возвращает объект <see cref="T:System.String"/>, который представляет текущий объект <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>Объект <see cref="T:System.String"/>, представляющий текущий объект <see cref="T:System.Object"/>.</returns>
        /// <filterpriority>2</filterpriority>
        [Pure, System.Diagnostics.DebuggerStepThrough]
        public override string ToString()
        {
            return string.Format("{0}{2};{3}{1}", f_MinInclude ? "[" : "(", f_MaxInclude ? "]" : ")", f_Min, f_Max);
        }

        #endregion

        /* ------------------------------------------------------------------------------------------ */

        #region Операторы

        [Pure, System.Diagnostics.DebuggerStepThrough]
        public static bool operator ==(Interval<T> left, Interval<T> right)
        {
            return (ReferenceEquals(null, left) && ReferenceEquals(null, right))
                || !ReferenceEquals(null, left) && left.Equals(right);
        }

        [Pure, System.Diagnostics.DebuggerStepThrough]
        public static bool operator !=(Interval<T> left, Interval<T> right) { return !(left == right); }

        /// <summary>Оператор неявного приведения типа к предикату</summary>
        /// <param name="I">Интервал</param>
        /// <returns>Предикат от вещественного типа двойной точности</returns>
        [Pure, System.Diagnostics.DebuggerStepThrough]
        public static implicit operator Predicate<T>(Interval<T> I)
        {
            Contract.Requires(I != null);
            Contract.Ensures(Contract.Result<Predicate<T>>() != null);
            return I.Check;
        }

        /// <summary>Оператор проверки на вхоождение величины в интервал</summary>
        /// <param name="Value">Проверяемая величина</param>
        /// <param name="I">Интервал</param>
        /// <returns>Истина, если величина внутри интервала</returns>
        [Pure, System.Diagnostics.DebuggerStepThrough]
        public static bool operator ^(T Value, Interval<T> I)
        {
            Contract.Requires(I != null);
            Contract.Requires(Value != null);
            return I.Check(Value);
        }
        /// <summary>Оператор проверки на вхоождение величины в интервал</summary>
        /// <param name="Value">Проверяемая величина</param>
        /// <param name="I">Интервал</param>
        /// <returns>Истина, если величина внутри интервала</returns>
        [Pure, System.Diagnostics.DebuggerStepThrough]
        public static bool operator ^(Interval<T> I, T Value)
        {
            Contract.Requires(I != null);
            Contract.Requires(Value != null);
            return Value ^ I;
        }

        [Pure, System.Diagnostics.DebuggerStepThrough]
        public static bool operator >(Interval<T> I, T Value)
        {
            Contract.Requires(I != null);
            Contract.Requires(Value != null);
            var result = I.f_Min.CompareTo(Value);
            return (result == 0 && !I.f_MinInclude) || result > 0;
        }
        [Pure, System.Diagnostics.DebuggerStepThrough]
        public static bool operator <(Interval<T> I, T Value)
        {
            Contract.Requires(I != null);
            Contract.Requires(Value != null);
            var result = I.f_Max.CompareTo(Value);
            return (result == 0 && !I.f_MaxInclude) || result < 0;
        }

        [Pure, System.Diagnostics.DebuggerStepThrough]
        public static bool operator >(T Value, Interval<T> I)
        {
            Contract.Requires(I != null);
            Contract.Requires(Value != null);
            var result = Value.CompareTo(I.f_Max);
            return (result == 0 && !I.f_MaxInclude) || result > 0;
        }
        [Pure, System.Diagnostics.DebuggerStepThrough]
        public static bool operator <(T Value, Interval<T> I)
        {
            Contract.Requires(I != null);
            Contract.Requires(Value != null);
            var result = Value.CompareTo(I.f_Min);
            return (result == 0 && !I.f_MinInclude) || result < 0;
        }

        #endregion

        /* ------------------------------------------------------------------------------------------ */

        [ContractInvariantMethod]
        private void ContractInvariant()
        {
            Contract.Invariant(f_Min != null, "Нарушение контракта f_Min != null");
            Contract.Invariant(f_Max != null, "Нарушение контракта f_Max != null");
        }

        /* ------------------------------------------------------------------------------------------ */
    }

    /// <summary>Интервал вещественых значений двойной точности</summary>
    [Serializable]
    [System.ComponentModel.TypeConverter(typeof(IntervalConverter))]
    public class Interval : Interval<double>, IComparable<double>
    {
        /* -------------------------------------------------------------------------------------------- */

        /// <summary>Метод сравнения двух интервалов</summary>
        /// <param name="a">Первый сравниваемый интервал</param>
        /// <param name="b">Второй сравниваемый интервал</param>
        /// <returns>1 - если первый интервал больше второго, -1 - если первый интервал меньше второго, 0 - если интервалы равны</returns>
        public static int Comparer_Length(Interval a, Interval b)
        {
            var l1 = a.Length;
            var l2 = b.Length;
            return l1 > l2 ? 1 : (l1 < l2 ? -1 : 0);
        }

        /* -------------------------------------------------------------------------------------------- */

        #region Свойства

        /// <summary>Длина интервала</summary>
        public double Length
        {
            get { return Max - Min; }
            set
            {
                var lv_M = Middle;
                value /= 2;
                Min = lv_M - value;
                Max = lv_M + value;
            }
        }

        /// <summary>Середина интервала</summary>
        public double Middle
        {
            get { return (Min + Max) / 2; }
            set
            {
                var lv_L2 = Length / 2;
                Min = value - lv_L2;
                Max = value + lv_L2;
            }
        }

        #endregion

        /* -------------------------------------------------------------------------------------------- */

        #region Конструкторы

        /// <summary>Интервал</summary>
        ///// <param name="Max">Верхняя граница интервала</param>
        //public Interval(double Max) : base(Max) { }

        /// <summary>Интервал</summary>
        /// <param name="Min">Нижняя граница интервала</param>
        /// <param name="Max">Верзняя граница интервала</param>
        public Interval(double Min, double Max) : base(Min, Max) { }

        /// <summary>Интервал</summary>
        /// <param name="Min">Нижняя граница интервала</param>
        /// <param name="Max">Верхняя граница интервала</param>
        /// <param name="IncludeLimits">Включать пределы?</param>
        public Interval(double Min, double Max, bool IncludeLimits)
            : base(Min, Max, IncludeLimits) { }

        /// <summary>Интервал</summary>
        /// <param name="Min">Нижняя граница интервала</param>
        /// <param name="MinInclude">Включена ли нижняя граница интервала?</param>
        /// <param name="Max">Верхняя граница интервала</param>
        public Interval(double Min, bool MinInclude, double Max) : base(Min, MinInclude, Max) { }

        /// <summary>Интервал</summary>
        /// <param name="Min">Нижняя граница интервала</param>
        /// <param name="MinInclude">Включена ли нижняя граница интервала?</param>
        /// <param name="Max">Верхняя граница интервала</param>
        /// <param name="MaxInclude">Включена ли верхняя граница интервала</param>
        public Interval(double Min, bool MinInclude, double Max, bool MaxInclude) : base(Min, MinInclude, Max, MaxInclude) { }

        #endregion

        /* -------------------------------------------------------------------------------------------- */

        #region Интервальные функции

        ///// <summary>Проверка на входжение в интервал</summary>
        ///// <param name="X">Проверяемая величина</param>
        ///// <returns></returns>
        //public bool Check(double X)
        //{
        //    return (MinInclude && Math.Abs(X - Min) < double.Epsilon)
        //        || (MaxInclude && Math.Abs(X - Max) < double.Epsilon)
        //        || (X > Min && X < Max);
        //}

        public bool Check(double X, double MinOffset, double MaxOffset)
        {
            var lv_Min = Min + MinOffset;
            var lv_Max = Max + MaxOffset;

            return (MinInclude && Math.Abs(X - lv_Min) < double.Epsilon)
                || (MaxInclude && Math.Abs(X - lv_Max) < double.Epsilon)
                || (X > lv_Min && X < lv_Max);
        }

        public bool Check(double X, double Offset) { return Check(X, Offset, -Offset); }

        public bool IsExclude(Interval I) { return !IsInclude(I); }

        public bool IsInclude(Interval I)
        {
            return Check(I.MinInclude ? I.Min : I.Min + double.Epsilon)
                && Check(I.MaxInclude ? I.Max : I.Max - double.Epsilon);
        }

        public bool IsIntersect(Interval I)
        {
            if(Math.Abs(I.Min - Min) < double.Epsilon && Math.Abs(I.Max - Max) < double.Epsilon) return true;

            var lv_MinInclude = Check(I.Min) || Math.Abs(I.Min - Max) > double.Epsilon;
            //if(!I.MinInclude && Math.Abs(I.Min - Max) < double.Epsilon) lv_MinInclude = false;

            var lv_MaxInclude = Check(I.Max) || Math.Abs(I.Max - Min) > double.Epsilon;
            //if(!I.MaxInclude && Math.Abs(I.Max - Min) < double.Epsilon) lv_MaxInclude = false;

            return lv_MinInclude || lv_MaxInclude;
        }

        #endregion

        public int CompareTo(double x)
        {
            return (x > Min && x < Max)
                   || (MinInclude && Math.Abs(Min - x) < double.Epsilon)
                   || (MaxInclude && Math.Abs(Max - x) < double.Epsilon)
                       ? 0
                       : (x < Min ? -1 : 1);
        }

        #region Цыклы

        public void For(int samples, Action<double> Do)
        {
            Contract.Requires(Do != null);
            Contract.Requires(samples > 0);

            var len = Length;
            var min = Min;
            if(!MaxInclude) len -= double.Epsilon;
            if(!MinInclude)
            {
                len -= double.Epsilon;
                min += double.Epsilon;
            }
            var dx = len / (samples - 1);
            for(var i = 0; i < samples; i++)
                Do(min + i * dx);
        }

        public void For(int samples, Action<int, double> Do)
        {
            Contract.Requires(Do != null);
            Contract.Requires(samples > 0);

            var len = Length;
            var min = Min;
            if(!MaxInclude) len -= double.Epsilon;
            if(!MinInclude)
            {
                len -= double.Epsilon;
                min += double.Epsilon;
            }
            var dx = len / (samples - 1);
            for(var i = 0; i < samples; i++)
                Do(i, min + i * dx);
        }

        public void WhileInInterval(double step, Action<double> Do)
        {
            var min = Math.Min(Max, Min);
            step = Max < Min && step > 0 ? -step : step;
            var x = min + (MinInclude ? 0 : double.Epsilon);
            while(Check(x)) Do(x += step);
        }

        #endregion

        public double[] GetValues(int Count)
        {
            var result = new double[Count];
            For(Count, (i, x) => result[i] = x);
            return result;
        }

        public new Interval GetInvertedInterval() { return new Interval(Max, MaxInclude, Min, MinInclude); }

        /* ------------------------------------------------------------------------------------------ */


        public static implicit operator double(Interval I) { return I.Length; }

        public static explicit operator Interval(double V) { return new Interval(0, true, V, true); }

        /* ------------------------------------------------------------------------------------------ */
    }
}
